/*Copyright*/
package com.jmex.physics.util;

import com.jme.input.InputHandler;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.intersection.PickData;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.system.DisplaySystem;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.Joint;
import com.jmex.physics.PhysicsSpace;

/**
 * A small tool to be able to pick the visual of physics nodes and move them around with the mouse.
 *
 * @author Irrisor
 */
public class PhysicsPicker {
    /**
     * root node of the scene - used for picking.
     */
    private final Node rootNode;
    /**
     * helper no the picked node is joined to.
     */
    private final DynamicPhysicsNode myNode;
    /**
     * joint to link myNode and picked node.
     */
    private final Joint joint;
    /**
     * joint to fix myNode in the world.
     */
    private final Joint worldJoint;

    /**
     * Constructor of the class.
     *
     * @param input        where {@link #getInputHandler()} is attached to
     * @param rootNode     root node of the scene - used for picking
     * @param physicsSpace physics space to create joints in (picked nodes must reside in there)
     */
    public PhysicsPicker( InputHandler input, Node rootNode, PhysicsSpace physicsSpace ) {
        this.inputHandler = new InputHandler();
        input.addToAttachedHandlers( this.inputHandler );
        this.rootNode = rootNode;
        joint = physicsSpace.createJoint();
        joint.setSpring( 2000, 200 );
        myNode = physicsSpace.createDynamicNode();
        myNode.setAffectedByGravity( false );
        worldJoint = physicsSpace.createJoint();
        activatePhysicsPicker();
    }

    /**
     * @return the input handler for this picker
     */
    public InputHandler getInputHandler() {
        return inputHandler;
    }

    private InputHandler inputHandler;

    private DynamicPhysicsNode picked;

    private final Vector2f mousePosition = new Vector2f();

    private void activatePhysicsPicker() {
        inputHandler.addAction( new InputAction() {
            private final Ray pickRay = new Ray();
            private final TrianglePickResults pickResults = new TrianglePickResults();

            public void performAction( InputActionEvent evt ) {
                if ( evt.getTriggerPressed() ) {
                    DisplaySystem.getDisplaySystem().getWorldCoordinates( mousePosition, 0, pickRay.origin );
                    DisplaySystem.getDisplaySystem().getWorldCoordinates( mousePosition, 0.3f, pickRay.direction );
                    pickRay.direction.subtractLocal( pickRay.origin ).normalizeLocal();

                    pickResults.clear();
                    pickResults.setCheckDistance( true );
                    rootNode.findPick( pickRay, pickResults );
                    loopResults:
                    for ( int i = 0; i < pickResults.getNumber(); i++ ) {
                        PickData data = pickResults.getPickData( i );
                        if ( data.getTargetTris().size() > 0 ) {
                            Spatial target = data.getTargetMesh();
                            while ( target != null ) {
                                if ( target instanceof DynamicPhysicsNode ) {
                                    DynamicPhysicsNode picked = (DynamicPhysicsNode) target;
                                    attach( picked );
                                    break loopResults;
                                }
                                target = target.getParent();
                            }
                        }
                    }
                }
                else {
                    release();
                }
            }
        }, InputHandler.DEVICE_MOUSE, 0, InputHandler.AXIS_NONE, false );

        inputHandler.addAction( new InputAction() {
            private final Vector3f anchor = new Vector3f();

            public void performAction( InputActionEvent evt ) {

                switch ( evt.getTriggerIndex() ) {
                    case 0:
                        mousePosition.x = evt.getTriggerPosition() * DisplaySystem.getDisplaySystem().getWidth();
                        break;
                    case 1:
                        mousePosition.y = evt.getTriggerPosition() * DisplaySystem.getDisplaySystem().getHeight();
                        break;
                    case 2:
                        // move into z direction with the wheel
                        if ( evt.getTriggerDelta() > 0 ) {
                            pickedScreenPos.z += ( 1 - pickedScreenPos.z ) / 10;
                        }
                        else {
                            pickedScreenPos.z = ( 10 * pickedScreenPos.z - 1 ) / 9;
                        }
                        break;
                }

                if ( picked != null ) {
                    DisplaySystem.getDisplaySystem().getWorldCoordinates( mousePosition, pickedScreenPos.z, anchor );
                    myNode.getLocalTranslation().set( anchor.subtractLocal( pickedWorldOffset ) );
                    worldJoint.setAnchor( myNode.getLocalTranslation() );
                    worldJoint.attach( myNode );
                }
            }
        }, InputHandler.DEVICE_MOUSE, InputHandler.BUTTON_NONE, InputHandler.AXIS_ALL, false );
    }

    private void release() {
        picked = null;
        joint.detach();
        worldJoint.detach();
        myNode.setActive( false );
    }

    private final Vector3f pickedScreenPos = new Vector3f();
    private final Vector3f pickedWorldOffset = new Vector3f();

    private void attach( DynamicPhysicsNode node ) {
        DisplaySystem.getDisplaySystem().getScreenCoordinates( node.getWorldTranslation(), pickedScreenPos );
        DisplaySystem.getDisplaySystem().getWorldCoordinates( mousePosition, pickedScreenPos.z, pickedWorldOffset );
        pickedWorldOffset.subtractLocal( node.getWorldTranslation() );

        picked = node;
        myNode.getLocalTranslation().set( node.getWorldTranslation() );
        myNode.setActive( true );
        worldJoint.setAnchor( myNode.getLocalTranslation() );
        worldJoint.attach( myNode );
        joint.attach( myNode, node );
        joint.setAnchor( new Vector3f() );
    }
}

/*
 * $log$
 */

